﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Oxage.Common;
using HorizontalAlignment = System.Windows.HorizontalAlignment;
using MessageBox = System.Windows.MessageBox;

namespace Oxage.Rasterizer.Models
{
	public class RasterViewModel : ModelBase
	{
		#region Private members
		private string documentPath = null;
		private FrameworkElement original = null;
		private IContractService contractService = null;
		protected delegate void CrossThreadDelegate();
		#endregion

		#region Constructor
		public RasterViewModel()
		{
			Clear();
		}
		#endregion

		#region Public properties
		/// <summary>
		/// Gets a value indicating whether debugging is enabled.
		/// </summary>
		public bool Debug
		{
			get
			{
				return Settings.Debug;
			}
		}

		/// <summary>
		/// Gets or sets a debug message.
		/// </summary>
		public string DebugMessage
		{
			get
			{
				return this.debugMessage;
			}
			set
			{
				if (this.debugMessage != value)
				{
					this.debugMessage = value;
					OnPropertyChanged("DebugMessage");
				}
			}
		}
		private string debugMessage;

		public VisualBrush PreviewBrush
		{
			get
			{
				return this.previewBrush;
			}
			set
			{
				if (this.previewBrush != value)
				{
					this.previewBrush = value;
					OnPropertyChanged("PreviewBrush");
					OnPropertyChanged("CanSave");
				}
			}
		}
		private VisualBrush previewBrush;

		public Stretch StretchType
		{
			get
			{
				return this.stretch;
			}
			set
			{
				if (this.stretch != value)
				{
					this.stretch = value;
					Settings.Stretch = value;
					OnPropertyChanged("StretchType");

					if (this.PreviewBrush != null)
					{
						this.PreviewBrush.Stretch = this.StretchType;
					}
				}
			}
		}
		private Stretch stretch;

		public List<Stretch> StretchList
		{
			get
			{
				return this.stretchList;
			}
			set
			{
				if (this.stretchList != value)
				{
					this.stretchList = value;
					OnPropertyChanged("StretchList");
				}
			}
		}
		private List<Stretch> stretchList;

		public VerticalAlignment VerticalAlignmentType
		{
			get
			{
				return this.verticalAlignment;
			}
			set
			{
				if (this.verticalAlignment != value)
				{
					this.verticalAlignment = value;
					Settings.VerticalAlignment = value;
					if (this.PreviewBrush != null)
					{
						this.PreviewBrush.AlignmentY = this.VerticalAlignmentY;
					}
					OnPropertyChanged("VerticalAlignmentType");
					OnPropertyChanged("VerticalAlignmentY");
				}
			}
		}
		private VerticalAlignment verticalAlignment;

		public List<VerticalAlignment> VerticalAlignmentList
		{
			get
			{
				return this.verticalAlignmentList;
			}
			set
			{
				if (this.verticalAlignmentList != value)
				{
					this.verticalAlignmentList = value;
					OnPropertyChanged("VerticalAlignmentList");
				}
			}
		}
		private List<VerticalAlignment> verticalAlignmentList;

		public HorizontalAlignment HorizontalAlignmentType
		{
			get
			{
				return this.horizontalAlignment;
			}
			set
			{
				if (this.horizontalAlignment != value)
				{
					this.horizontalAlignment = value;
					Settings.HorizontalAlignment = value;
					if (this.PreviewBrush != null)
					{
						this.PreviewBrush.AlignmentX = this.HorizontalAlignmentX;
					}
					OnPropertyChanged("HorizontalAlignmentType");
					OnPropertyChanged("HorizontalAlignmentX");
				}
			}
		}
		private HorizontalAlignment horizontalAlignment;

		public List<HorizontalAlignment> HorizontalAlignmentList
		{
			get
			{
				return this.horizontalAlignmentList;
			}
			set
			{
				if (this.horizontalAlignmentList != value)
				{
					this.horizontalAlignmentList = value;
					OnPropertyChanged("HorizontalAlignmentList");
				}
			}
		}
		private List<HorizontalAlignment> horizontalAlignmentList;

		public AlignmentX HorizontalAlignmentX
		{
			get
			{
				switch (this.HorizontalAlignmentType)
				{
					case HorizontalAlignment.Left:
						return AlignmentX.Left;
						break;

					case HorizontalAlignment.Right:
						return AlignmentX.Right;
						break;

					default:
					case HorizontalAlignment.Center:
					case HorizontalAlignment.Stretch:
						return AlignmentX.Center;
						break;
				}
			}
		}

		public AlignmentY VerticalAlignmentY
		{
			get
			{
				switch (this.VerticalAlignmentType)
				{
					case VerticalAlignment.Top:
						return AlignmentY.Top;
						break;

					case VerticalAlignment.Bottom:
						return AlignmentY.Bottom;
						break;

					default:
					case VerticalAlignment.Center:
					case VerticalAlignment.Stretch:
						return AlignmentY.Center;
						break;
				}
			}
		}

		public List<bool> TrueFalse
		{
			get
			{
				return this.trueFalse;
			}
			set
			{
				if (this.trueFalse != value)
				{
					this.trueFalse = value;
					OnPropertyChanged("TrueFalse");
				}
			}
		}
		private List<bool> trueFalse;

		public bool FlipHorizontal
		{
			get
			{
				return this.flipHorizontal && this.OutputFormat.Extension == ".jpg";
			}
			set
			{
				if (this.flipHorizontal != value)
				{
					this.flipHorizontal = value;
					Settings.JpegFlipHorizontal = value;
					OnPropertyChanged("FlipHorizontal");
				}
			}
		}
		private bool flipHorizontal;

		public bool FlipVertical
		{
			get
			{
				return this.flipVertical && this.OutputFormat.Extension == ".jpg";
			}
			set
			{
				if (this.flipVertical != value)
				{
					this.flipVertical = value;
					Settings.JpegFlipVertical = value;
					OnPropertyChanged("FlipVertical");
				}
			}
		}
		private bool flipVertical;

		public int JpegQualityLevel
		{
			get
			{
				return this.jpegQualityLevel;
			}
			set
			{
				if (this.jpegQualityLevel != value)
				{
					if (value < 0)
					{
						this.jpegQualityLevel = 0;
					}
					else if (value > 100)
					{
						this.jpegQualityLevel = 100;
					}
					else
					{
						this.jpegQualityLevel = value;
					}
					Settings.JpegQualityLevel = this.jpegQualityLevel;
					OnPropertyChanged("JpegQualityLevel");
				}
			}
		}
		private int jpegQualityLevel;

		public RotationModel ImageRotation
		{
			get
			{
				return this.imageRotation;
			}
			set
			{
				if (this.imageRotation != value)
				{
					this.imageRotation = value;
					if (value != null)
					{
						Settings.ImageRotation = value.Rotation;
					}
					OnPropertyChanged("ImageRotation");
				}
			}
		}
		private RotationModel imageRotation;

		public List<RotationModel> RotationList
		{
			get
			{
				return this.rotationList;
			}
			set
			{
				if (this.rotationList != value)
				{
					this.rotationList = value;
					OnPropertyChanged("RotationList");
				}
			}
		}
		private List<RotationModel> rotationList;

		public TiffCompressOption TiffCompression
		{
			get
			{
				return this.tiffCompression;
			}
			set
			{
				if (this.tiffCompression != value)
				{
					this.tiffCompression = value;
					Settings.TiffCompression = value;
					OnPropertyChanged("TiffCompression");
				}
			}
		}
		private TiffCompressOption tiffCompression;

		public List<TiffCompressOption> TiffCompressionList
		{
			get
			{
				return this.tiffCompressList;
			}
			set
			{
				if (this.tiffCompressList != value)
				{
					this.tiffCompressList = value;
					OnPropertyChanged("TiffCompressionList");
				}
			}
		}
		private List<TiffCompressOption> tiffCompressList;

		public double OutputWidth
		{
			get
			{
				return this.outputWidth;
			}
			set
			{
				if (this.outputWidth != value)
				{
					this.outputWidth = value;
					FindMatchingOutputSize();
					OnPropertyChanged("OutputWidth");
				}
			}
		}

		private double outputWidth;

		public double OutputHeight
		{
			get
			{
				return this.outputHeight;
			}
			set
			{
				if (this.outputHeight != value)
				{
					this.outputHeight = value;
					FindMatchingOutputSize();
					OnPropertyChanged("OutputHeight");
				}
			}
		}
		private double outputHeight;

		public OutputSize OutputSize
		{
			get
			{
				return this.outputSize;
			}
			set
			{
				if (this.outputSize != value)
				{
					if (value != null && !value.IsCustom)
					{
						this.OutputWidth = this.OriginalWidth * value.Scale;
						this.OutputHeight = this.OriginalHeight * value.Scale;
					}

					this.outputSize = value;
					OnPropertyChanged("OutputSize");
				}
			}
		}
		private OutputSize outputSize;

		public List<OutputSize> OutputSizeList
		{
			get
			{
				return this.outputSizeList;
			}
			set
			{
				if (this.outputSizeList != value)
				{
					this.outputSizeList = value;
					OnPropertyChanged("OutputSizeList");
				}
			}
		}
		private List<OutputSize> outputSizeList;

		public OutputFormat OutputFormat
		{
			get
			{
				return this.outputFormat;
			}
			set
			{
				if (this.outputFormat != value)
				{
					this.outputFormat = value;
					if (value != null)
					{
						Settings.OutputFormatExtension = value.Extension;
					}
					OnPropertyChanged("OutputFormat");
				}
			}
		}
		private OutputFormat outputFormat;

		public List<OutputFormat> OutputFormatList
		{
			get
			{
				return this.outputFormatList;
			}
			set
			{
				if (this.outputFormatList != value)
				{
					this.outputFormatList = value;
					OnPropertyChanged("OutputFormatList");
				}
			}
		}
		private List<OutputFormat> outputFormatList;

		public ICommand LoadCommand
		{
			get
			{
				return this.loadCommand;
			}
			set
			{
				if (this.loadCommand != value)
				{
					this.loadCommand = value;
					OnPropertyChanged("LoadCommand");
				}
			}
		}
		private ICommand loadCommand;

		public ICommand SaveCommand
		{
			get
			{
				return this.saveCommand;
			}
			set
			{
				if (this.saveCommand != value)
				{
					this.saveCommand = value;
					OnPropertyChanged("SaveCommand");
				}
			}
		}
		private ICommand saveCommand;

		public ICommand RefreshCommand
		{
			get
			{
				return this.refreshCommand;
			}
			set
			{
				if (this.refreshCommand != value)
				{
					this.refreshCommand = value;
					OnPropertyChanged("RefreshCommand");
				}
			}
		}
		private ICommand refreshCommand;

		public ICommand HyperlinkCommand
		{
			get
			{
				return this.hyperlinkCommand;
			}
			set
			{
				if (this.hyperlinkCommand != value)
				{
					this.hyperlinkCommand = value;
					OnPropertyChanged("HyperlinkCommand");
				}
			}
		}
		private ICommand hyperlinkCommand;

		public double OriginalWidth
		{
			get
			{
				return this.contentWidth;
			}
			set
			{
				if (this.contentWidth != value)
				{
					this.contentWidth = value;
					OnPropertyChanged("OriginalWidth");
				}
			}
		}
		private double contentWidth;

		public double OriginalHeight
		{
			get
			{
				return this.contentHeight;
			}
			set
			{
				if (this.contentHeight != value)
				{
					this.contentHeight = value;
					OnPropertyChanged("OriginalHeight");
				}
			}
		}
		private double contentHeight;

		/// <summary>
		/// Gets a value indicating whether exporting can be performed.
		/// </summary>
		public bool CanSave
		{
			get
			{
				return this.PreviewBrush != null;
			}
		}

		/// <summary>
		/// Gets a value indicating whether the module is in development mode.
		/// </summary>
		public bool IsDebug
		{
			get
			{
#if DEBUG
				return true;
#else
				return false;
#endif
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether the module is hosted in a stand-alone application (i.e. not as Expression Blend plugin).
		/// </summary>
		public bool IsStandalone
		{
			get
			{
				return this.isStandalone;
			}
			set
			{
				if (this.isStandalone != value)
				{
					this.isStandalone = value;
					OnPropertyChanged("IsStandalone");
				}
			}
		}
		private bool isStandalone;

		public Exception LastError
		{
			get
			{
				return this.lastError;
			}
			set
			{
				if (this.lastError != value)
				{
					this.lastError = value;
					OnPropertyChanged("LastError");
					OnPropertyChanged("HasError");
				}
			}
		}
		private Exception lastError;

		public bool HasError
		{
			get
			{
				return this.LastError != null;
			}
		}
		#endregion

		#region Public methods
		public void Clear()
		{
			this.LastError = null;

			this.JpegQualityLevel = Settings.JpegQualityLevel;
			this.FlipHorizontal = Settings.JpegFlipHorizontal;
			this.FlipVertical = Settings.JpegFlipVertical;

			this.TrueFalse = new List<bool>();
			this.TrueFalse.Add(true);
			this.TrueFalse.Add(false);

			this.RotationList = new List<RotationModel>();
			this.RotationList.Add(new RotationModel() { Name = "0°", Rotation = Rotation.Rotate0 });
			this.RotationList.Add(new RotationModel() { Name = "90°", Rotation = Rotation.Rotate90 });
			this.RotationList.Add(new RotationModel() { Name = "180°", Rotation = Rotation.Rotate180 });
			this.RotationList.Add(new RotationModel() { Name = "270°", Rotation = Rotation.Rotate270 });
			this.ImageRotation = this.RotationList.FirstOrDefault(x => x.Rotation == Settings.ImageRotation);

			this.TiffCompressionList = new List<TiffCompressOption>();
			this.TiffCompressionList.Add(TiffCompressOption.None);
			this.TiffCompressionList.Add(TiffCompressOption.Default);
			this.TiffCompressionList.Add(TiffCompressOption.Ccitt3);
			this.TiffCompressionList.Add(TiffCompressOption.Ccitt4);
			this.TiffCompressionList.Add(TiffCompressOption.Lzw);
			this.TiffCompressionList.Add(TiffCompressOption.Rle);
			this.TiffCompressionList.Add(TiffCompressOption.Zip);
			this.TiffCompression = Settings.TiffCompression;

			this.OutputSizeList = new List<OutputSize>();
			this.OutputSizeList.Add(new OutputSize() { Name = "50%", Scale = 0.5 });
			this.OutputSizeList.Add(new OutputSize() { Name = "100% (original)", Scale = 1.0 });
			this.OutputSizeList.Add(new OutputSize() { Name = "200%", Scale = 2.0 });
			this.OutputSizeList.Add(new OutputSize() { Name = "Custom", Scale = 1.0, IsCustom = true });

			this.OutputFormatList = new List<OutputFormat>();
			this.OutputFormatList.Add(new OutputFormat() { Name = "Bitmap (*.bmp)", Extension = ".bmp" });
			this.OutputFormatList.Add(new OutputFormat() { Name = "JPEG (*.jpg)", Extension = ".jpg" });
			this.OutputFormatList.Add(new OutputFormat() { Name = "PNG (*.png)", Extension = ".png" });
			this.OutputFormatList.Add(new OutputFormat() { Name = "GIF (*.gif)", Extension = ".gif" });
			this.OutputFormatList.Add(new OutputFormat() { Name = "TIFF (*.tif)", Extension = ".tif" });

			this.StretchList = new List<Stretch>();
			this.StretchList.Add(Stretch.None);
			this.StretchList.Add(Stretch.Fill);
			this.StretchList.Add(Stretch.Uniform);
			this.StretchList.Add(Stretch.UniformToFill);

			this.HorizontalAlignmentList = new List<HorizontalAlignment>();
			this.HorizontalAlignmentList.Add(HorizontalAlignment.Left);
			this.HorizontalAlignmentList.Add(HorizontalAlignment.Center);
			this.HorizontalAlignmentList.Add(HorizontalAlignment.Right);
			//this.HorizontalAlignmentList.Add(HorizontalAlignment.Stretch);

			this.VerticalAlignmentList = new List<VerticalAlignment>();
			this.VerticalAlignmentList.Add(VerticalAlignment.Top);
			this.VerticalAlignmentList.Add(VerticalAlignment.Center);
			this.VerticalAlignmentList.Add(VerticalAlignment.Bottom);
			//this.VerticalAlignmentList.Add(VerticalAlignment.Stretch);

			this.StretchType = Settings.Stretch;
			this.HorizontalAlignmentType = Settings.HorizontalAlignment;
			this.VerticalAlignmentType = Settings.VerticalAlignment;
			this.OutputSize = this.OutputSizeList.FirstOrDefault(x => x.Scale == 1.0) ?? this.OutputSizeList.FirstOrDefault();
			this.OutputFormat = this.OutputFormatList.FirstOrDefault(x => x.Extension == Settings.OutputFormatExtension) ?? this.OutputFormatList.FirstOrDefault();
			this.LoadCommand = new LoadCommand(this);
			this.SaveCommand = new SaveCommand(this);
			this.RefreshCommand = new RefreshCommand(this);
			this.HyperlinkCommand = new HyperlinkCommand(this);

			if (Settings.Debug)
			{
				this.DebugMessage = "Successfully initialized.";
			}
		}

		public void Load(string path)
		{
			try
			{
				using (var fs = File.OpenRead(path))
				{
					Load(XamlReader.Load(fs) as FrameworkElement);
				}

				this.documentPath = path;
			}
			catch (Exception ex)
			{
				ReportError(ex);
			}
		}

		public void Load(FrameworkElement element)
		{
			if (element != null)
			{
				this.OriginalWidth = element.Width;
				this.OriginalHeight = element.Height;

				//Create brush for a preview
				VisualBrush brush = new VisualBrush(element);
				brush.AlignmentX = this.HorizontalAlignmentX;
				brush.AlignmentY = this.VerticalAlignmentY;
				brush.AutoLayoutContent = true;
				brush.Stretch = this.StretchType;
				brush.TileMode = TileMode.None;
				this.PreviewBrush = brush;

				//Show preview
				if (this.OutputSize != null && !this.OutputSize.IsCustom)
				{
					this.OutputWidth = this.OriginalWidth * this.OutputSize.Scale;
					this.OutputHeight = this.OriginalHeight * this.OutputSize.Scale;
				}

				this.original = element;
			}
		}

		public void BrowseAndLoad()
		{
			OpenFileDialog dialog = new OpenFileDialog();
			dialog.Filter = "*.xaml|*.xaml";
			dialog.Multiselect = false;

			if (dialog.ShowDialog() == DialogResult.OK)
			{
				Load(dialog.FileName);
			}
		}

		public void BrowseAndSave()
		{
			//Build filter list
			List<string> extensions = new List<string>();
			StringBuilder builder = new StringBuilder();
			foreach (var filter in this.OutputFormatList)
			{
				if (builder.Length > 0)
				{
					builder.Append("|");
				}
				extensions.Add(filter.Extension);
				builder.Append(filter.GetDialogFilterName());
			}

			//Create a dialog
			string ext = this.OutputFormat.Extension;
			SaveFileDialog dialog = new SaveFileDialog();
			dialog.FileName = Path.ChangeExtension(this.documentPath, ext); //Suggested name
			dialog.Filter = builder.ToString(); //List of available output formats
			dialog.FilterIndex = extensions.IndexOf(ext) + 1;

			if (dialog.ShowDialog() == DialogResult.OK)
			{
				string path = dialog.FileName;
				string name = Path.GetFileNameWithoutExtension(path);
				string xamlFileName = Path.GetFileNameWithoutExtension(this.documentPath ?? "unknown.xaml");

				try
				{
					XamlConverter converter = new XamlConverter();

					//BitmapMetadata meta = null;
					BitmapEncoder encoder = null;
					switch (extensions[dialog.FilterIndex - 1])
					{
						case ".jpg":
							var jpegEncoder = new JpegBitmapEncoder();
							//jpegEncoder.Rotation = this.ImageRotation.Rotation;
							//jpegEncoder.FlipHorizontal = this.FlipHorizontal;
							//jpegEncoder.FlipVertical = this.FlipVertical;
							jpegEncoder.QualityLevel = this.JpegQualityLevel;
							encoder = jpegEncoder;
							//meta = new BitmapMetadata("jpeg");
							break;

						case ".png":
							var pngEncoder = new PngBitmapEncoder();
							//pngEncoder.Interlace
							encoder = pngEncoder;
							//meta = new BitmapMetadata("png");
							break;

						case ".gif":
							var gifEncoder = new GifBitmapEncoder();
							encoder = gifEncoder;
							//meta = new BitmapMetadata("gif");
							break;

						case ".tif":
							var tiffEncoder = new TiffBitmapEncoder();
							tiffEncoder.Compression = this.TiffCompression;
							encoder = tiffEncoder;
							//meta = new BitmapMetadata("tiff");
							break;

						case ".bmp":
							var bmpEncoder = new BmpBitmapEncoder();
							encoder = bmpEncoder;
							break;
					}

					if (encoder == null)
					{
						throw new ArgumentNullException("encoder", "Image encoder cannot be determined!");
					}

					//if (encoder.Metadata != null)
					//{
					//  encoder.Metadata.Title = name;
					//  encoder.Metadata.Comment = "Converted from " + xamlFileName;
					//  encoder.Metadata.ApplicationName = Settings.ApplicationName;
					//}

					converter.XamlToImage(this.PreviewBrush, path, (int)this.OutputWidth, (int)this.OutputHeight, this.StretchType, encoder);
				}
				catch (Exception ex)
				{
					ReportError(ex);
				}
			}
		}

		public void RefreshPreview()
		{
			Load(this.original);
		}

		/// <summary>
		/// Loads a document that has been opened in Expression Blend.
		/// </summary>
		/// <param name="contractService"></param>
		public void RegisterContractService(IContractService contractService)
		{
			this.contractService = contractService;
			this.contractService.DebugMessage += (sender, e) =>
			{
				System.Windows.Application.Current.Dispatcher.BeginInvoke(new CrossThreadDelegate(() =>
				{
					this.DebugMessage = e.Result;
				}));
			};
			this.contractService.DocumentChanged += (sender, e) =>
			{
				RefreshDocument();

			};
			RefreshDocument();
		}

		protected void RefreshDocument()
		{
			if (this.contractService.DocumentContent != null)
			{
				this.documentPath = this.contractService.DocumentPath;
				Load(this.contractService.DocumentContent);

				if (this.contractService.ProjectType == ProjectTypes.Silverlight)
				{
					this.LastError = new Exception("Silverlight project type is not supported. Try WPF.");
				}
				else if (this.contractService.ProjectType == ProjectTypes.Silverlight)
				{
					this.LastError = new Exception("This project type may not be supported. Try WPF.");
				}
				else
				{
					this.LastError = null;
				}
			}
		}
		#endregion

		#region Protected methods
		protected void FindMatchingOutputSize()
		{
			if (this.OriginalWidth > 0 && this.OriginalHeight > 0)
			{
				double scaleX = this.OutputWidth / this.OriginalWidth;
				double scaleY = this.OutputHeight / this.OriginalHeight;

				var outputSize = (scaleX == scaleY ? this.OutputSizeList.FirstOrDefault(x => x.Scale == scaleX) : null);
				if (outputSize == null)
				{
					outputSize = this.OutputSizeList.FirstOrDefault(x => x.IsCustom);
				}

				this.OutputSize = outputSize;
			}
		}

		protected void ReportError(Exception error)
		{
			this.LastError = error;

			if (this.IsStandalone && error != null)
			{
				MessageBox.Show(error.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
			}
			
			if (this.contractService != null && error != null)
			{
				this.contractService.WriteLine(error.Message);
			}
		}
		#endregion
	}
}
